import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class SymbolTableImpl implements SymbolTable {

	private HashMap<String, List<NameBlock>> table;

	public SymbolTableImpl() {
		table = new HashMap<String, List<NameBlock>>();
	}

	// Utility function to return the entity block for a name.
	private EntityBlock getLastEntity(String name) {
		List<NameBlock> nameBlocks = table.get(name);

		// No entry in the hash map.
		if (nameBlocks == null) {
			return null;
		}

		// Get the name block.
		NameBlock lookupNameBlock = null;
		for (NameBlock nameBlock : nameBlocks) {
			if (nameBlock.getName().compareTo(name) != 0) {
				continue;
			}
			lookupNameBlock = nameBlock;
			break;
		}

		// Return null if no name block is found.
		if (lookupNameBlock == null) {
			return null;
		}

		EntityBlock curEntityBlk = lookupNameBlock.getEntityBlock();
		while (curEntityBlk != null) {
			if (curEntityBlk.getNameLink() == null) {
				break;
			}
			curEntityBlk = curEntityBlk.getNameLink();
		}

		// Return null if not found.
		return curEntityBlk;
	}

	// Utility function to insert a name block (or)
	// to update the entity block of a name block.
	private void InsertIntoTable(NameBlock nameBlock) {
		String name = nameBlock.getName();
		List<NameBlock> nameBlocks = table.get(name);

		// No entry in the hash map.
		if (nameBlocks == null) {
			nameBlocks = new ArrayList<NameBlock>();
			nameBlocks.add(nameBlock);
			table.put(name, nameBlocks);
		}

		// Get the name block.
		NameBlock insertNameBlock = null;
		for (NameBlock nBlk : nameBlocks) {
			if (nBlk.getName().compareTo(name) != 0) {
				continue;
			}
			insertNameBlock = nBlk;
			break;
		}

		// Update the entity block pointed to if name block already exists.
		if (insertNameBlock != null) {
			EntityBlock insertEntityBlock = nameBlock.getEntityBlock();
			insertNameBlock.setEntityBlock(insertEntityBlock);
			insertEntityBlock.setNameBlock(insertNameBlock);
		} else {
			nameBlocks.add(nameBlock);
		}
	}

	// Utility function to reset the name block links
	// appropriately.
	private void ResetTableEntry(EntityBlock entityBlock) {
		EntityKind kind = entityBlock.getKind();
		EntityBlock nameLink = entityBlock.getNameLink();
		boolean resetDone = false;

		// Remove the name block if this was the only entity.
		if (nameLink == null) {
			NameBlock nameBlock = entityBlock.getNameBlock();
			List<NameBlock> nameBlocks = table.get(nameBlock.getName());
			nameBlocks.remove(nameBlock);
		}

		while (nameLink != null && resetDone == false) {
			EntityKind curKind = nameLink.getKind();
			if (curKind.compareTo(kind) == 0) {
				NameBlock nameBlock = nameLink.getNameBlock();
				nameBlock.setEntityBlock(nameLink);
				resetDone = true;
			}
			nameLink = nameLink.getNameLink();
		}
	}

	public EntityBlock lookup(String name, EntityKind kind,
			MutableInt isCurScope) {
		List<NameBlock> nameBlocks = table.get(name);

		// No entry in the hash map.
		if (nameBlocks == null) {
			return null;
		}

		// Get the name block.
		NameBlock lookupNameBlock = null;
		for (NameBlock nameBlock : nameBlocks) {
			if (nameBlock.getName().compareTo(name) != 0) {
				continue;
			}
			lookupNameBlock = nameBlock;
			break;
		}

		// Return null if no name block is found.
		if (lookupNameBlock == null) {
			return null;
		}

		EntityBlock curEntityBlk = lookupNameBlock.getEntityBlock();
		while (curEntityBlk != null) {
			if (curEntityBlk.getKind().compareTo(kind) == 0) {
				ScopeManager scopeMgr = ScopeManager.getScopeManager();
				isCurScope.setValue(0);
				if (scopeMgr.getCurrentScopeCount() == curEntityBlk
						.getScopeCount()) {
					isCurScope.setValue(1);
				}
				return curEntityBlk;
			}
			curEntityBlk = curEntityBlk.getNameLink();
		}

		// Return null if not found.
		return null;
	}

	public EntityBlock insert(String name, EntityKind kind) {
		MutableInt isCurScope = new MutableInt(-1);
		// Check if the variable name exists and in current scope
		EntityBlock entityBlock = lookup(name, kind, isCurScope);
		if(entityBlock != null && 
				isCurScope.getValue().equals(new Integer(1))){
			System.out.println("Error:: Multiple declaration of " + name);
		}
		NameBlock nameBlock = new NameBlock();
		entityBlock = new EntityBlock();

		// Get last entity block from this scope.
		ScopeManager scopeMgr = ScopeManager.getScopeManager();
		ScopeInfo lastScope = scopeMgr.getCurrentScope();
		EntityBlock curScopeEntityBlk = lastScope.getEntityBlock();

		// Set the scope stack value to this entity.
		lastScope.setEntityBlock(entityBlock);

		// Get the entity block with same name.
		EntityBlock lastNameBlock = getLastEntity(name);

		// Initialize entity block.
		entityBlock.setNameLink(lastNameBlock);
		entityBlock.setScopeLink(curScopeEntityBlk);
		entityBlock.setKind(kind);
		entityBlock.setScopeCount(scopeMgr.getCurrentScopeCount());
		entityBlock.setNameBlock(nameBlock);

		// Initialize name block.
		nameBlock.setName(name);
		nameBlock.setEntityBlock(entityBlock);

		// Insert the entity block to the list in hash table.
		InsertIntoTable(nameBlock);

		return entityBlock;
	}

	public void enterBlock() {
		ScopeManager scopeMgr = ScopeManager.getScopeManager();
		ScopeInfo lastScope = scopeMgr.createNewScope();
		lastScope.setEntityBlock(null);
	}

	public void leaveBlock() {
		ScopeManager scopeMgr = ScopeManager.getScopeManager();
		ScopeInfo scopeInfo = scopeMgr.popLastScope();
		EntityBlock lastScopeBlk = scopeInfo.getEntityBlock();

		while (lastScopeBlk != null) {
			ResetTableEntry(lastScopeBlk);
			lastScopeBlk = lastScopeBlk.getScopeLink();
		}
	}

	public void printSymbols() {
		System.out.println("Symbol Table");
		System.out.println("Name Kind  level");
		for (String entityName : table.keySet()) {
			List<NameBlock> nameBlocks = table.get(entityName);
			for (NameBlock nameBlock : nameBlocks) {
				EntityBlock entityBlk = nameBlock.getEntityBlock();
				while (entityBlk != null) {
					entityBlk.print();
					entityBlk = entityBlk.getNameLink();
				}
			}
		}
		System.out.println();
	}
}
